异常处理
1 2 3 4 5 try { }catch (exceptionType variable){ }
throw
关键字用来抛出一个异常,这个异常会被 try 检测到,进而被 catch 捕获
1 2 3 4 5 6 try { throw "Unknown Exception" ; cout <<"This statement will not be executed." <<endl ; }catch (const char * &e){ cout <<e<<endl ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 try { throw Derived(); cout <<"This statement will not be executed." <<endl ; }catch (int ){ cout <<"Exception type: int" <<endl ; }catch (char *){ cout <<"Exception type: cahr *" <<endl ; }catch (Base){ cout <<"Exception type: Base" <<endl ; }catch (Derived){ cout <<"Exception type: Derived" <<endl ; } 最终输出:Exception type: Base
catch 在匹配异常类型的过程中,也会进行类型转换,但是这种转换受到了更多的限制,仅能进行「向上转型」、「const 转换」和「数组或函数指针转换」,其他的都不能应用于 catch。
1 2 3 4 5 6 try { throw nums; cout <<"This statement will not be executed." <<endl ; }catch (const int *){ cout <<"Exception type: const int *" <<endl ; }
throw关键字
throw 关键字来显式地抛出异常,它的用法为:
throw exceptionData;
exceptionData 是“异常数据”的意思,它可以包含任意的信息,完全有程序员决定。exceptionData 可以是 int、float、bool 等基本类型,也可以是指针、数组、字符串、结构体、类等聚合类型,请看下面的例子:
1 2 3 4 5 6 7 8 纯文本复制 char str[] = "http://c.biancheng.net"; char *pstr = str;class Base{}; Base obj; throw 100; //int 类型 throw str; //数组类型 throw pstr; //指针类型 throw obj; //对象类型
throw 关键字除了可以用在函数体中抛出异常,还可以用在函数头和函数体之间,指明当前函数能够抛出的异常类型,这称为异常规范(Exception specification),有些教程也称为异常指示符或异常列表。请看下面的例子:
1 2 double func (char param) throw (int ) ;double func (char param) throw (int , char , exception) ;
异常规范在函数声明和函数定义中必须同时指明,并且要严格保持一致,不能更加严格或者更加宽松。
异常规范 不再建议使用
拷贝构造函数(复制构造函数)
1 2 3 4 5 6 7 8 9 10 11 public: Student(const Student &stu); //拷贝构造函数(声明) //拷贝构造函数(定义) Student::Student(const Student &stu){ this->m_name = stu.m_name; this->m_age = stu.m_age; this->m_score = stu.m_score; cout<<"Copy constructor was called."<<endl; }
如果程序员没有显式地定义拷贝构造函数,那么编译器会自动生成一个默认的拷贝构造函数。这个默认的拷贝构造函数很简单,就是使用“老对象”的成员变量对“新对象”的成员变量进行一一赋值,和上面 Student 类的拷贝构造函数非常类似。
深拷贝和浅拷贝
将 a 和 obj1 所在内存中的数据按照二进制位(Bit)复制到 b 和 obj2 所在的内存,这种默认的拷贝行为就是浅拷贝,这和调用 memcpy() 函数的效果非常类似。
1 2 3 4 int a = 10 ;int b = a; Base obj1 (10 , 20 ) ;Base obj2 = obj1;
将对象所持有的其它资源一并拷贝的行为叫做深拷贝,我们必须显式地定义拷贝构造函数才能达到深拷贝的目的。
1 2 3 4 5 Array::Array(const Array &arr){ this ->m_len = arr.m_len; this ->m_p = (int *)calloc ( this ->m_len, sizeof (int ) ); memcpy ( this ->m_p, arr.m_p, m_len * sizeof (int ) ); }
四种类型转换运算符
C++ 新增了四个关键字来予以支持类型转换:
关键字
说明
static_cast
用于良性转换,一般不会导致意外发生,风险很低。
const_cast
用于 const 与非 const、volatile 与非 volatile 之间的转换。
reinterpret_cast
高度危险的转换,这种转换仅仅是对二进制位的重新解释,不会借助已有的转换规则对数据进行调整,但是可以实现最灵活的 C++ 类型转换。
dynamic_cast
借助 RTTI,用于类型安全的向下转型(Downcasting)。
四个关键字的语法格式都是一样的,具体为:
newType 是要转换成的新类型,data 是被转换的数据。例如,老式的C风格的 double 转 int 的写法为:
1 2 double scores = 95.5; int n = (int)scores;
C++ 新风格的写法为:
1 2 3 纯文本复制 double scores = 95.5; int n = static_cast<int>(scores);
static_cast
static_cast 只能用于良性转换,这样的转换风险较低
原有的自动类型转换,例如 short 转 int、int 转 double、const 转非 const、向上转型等;
void 指针和具体类型指针之间的转换,例如void *
转int *
、char *
转void *
等;
有转换构造函数或者类型转换函数的类与其它类型之间的转换,例如 double 转 Complex(调用转换构造函数)、Complex 转 double(调用类型转换函数)。
需要注意的是,static_cast 不能用于无关类型之间的转换,因为这些转换都是有风险的,例如:
两个具体类型指针之间的转换,例如int *
转double *
、Student *
转int *
等。
int 和指针之间的转换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int main () { int m = 100 ; Complex c (12.5 , 23.8 ) ; long n = static_cast <long >(m); char ch = static_cast <char >(m); int *p1 = static_cast <int *>( malloc (10 * sizeof (int )) ); void *p2 = static_cast <void *>(p1); double real= static_cast <double >(c); float *p3 = static_cast <float *>(p1); p3 = static_cast <float *>(0X2DF9 ); return 0 ; }
const_cast 关键字
const_cast 比较好理解,它用来去掉表达式的 const 修饰或 volatile 修饰。换句话说,const_cast 就是用来将 const/volatile 类型转换为非 const/volatile 类型。
1 2 3 4 5 6 7 8 int main () { const int n = 100 ; int *p = const_cast <int *>(&n); *p = 234 ; cout <<"n = " <<n<<endl ; cout <<"*p = " <<*p<<endl ; return 0 ; }
reinterpret_cast 关键字
reinterpret 是“重新解释”的意思,顾名思义,reinterpret_cast 这种转换仅仅是对二进制位的重新解释,不会借助已有的转换规则对数据进行调整,非常简单粗暴,所以风险很高。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int main () { char str[]="http://c.biancheng.net" ; float *p1 = reinterpret_cast <float *>(str); cout <<*p1<<endl ; int *p = reinterpret_cast <int *>(100 ); p = reinterpret_cast <int *>(new A(25 , 96 )); cout <<*p<<endl ; return 0 ; }
dynamic_cast 关键字
dynamic_cast 用于在类的继承层次之间进行类型转换,它既允许向上转型(Upcasting),也允许向下转型(Downcasting)。向上转型是无条件的,不会进行任何检测,所以都能成功;向下转型的前提必须是安全的,要借助 RTTI 进行检测,所有只有一部分能成功。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Base {public : Base(int a = 0 ): m_a(a){ } int get_a () const { return m_a; } virtual void func () const { } protected : int m_a; }; class Derived : public Base{public : Derived(int a = 0 , int b = 0 ): Base(a), m_b(b){ } int get_b () const { return m_b; } private : int m_b; }; int main () { Derived *pd1 = new Derived(35 , 78 ); Base *pb1 = dynamic_cast <Derived*>(pd1); cout <<"pd1 = " <<pd1<<", pb1 = " <<pb1<<endl ; cout <<pb1->get_a()<<endl ; pb1->func(); int n = 100 ; Derived *pd2 = reinterpret_cast <Derived*>(&n); Base *pb2 = dynamic_cast <Base*>(pd2); cout <<"pd2 = " <<pd2<<", pb2 = " <<pb2<<endl ; cout <<pb2->get_a()<<endl ; pb2->func(); return 0 ; }
向下转型是有风险的,dynamic_cast 会借助 RTTI 信息进行检测,确定安全的才能转换成功,否则就转换失败。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int main () { A *pa = new A(); B *pb; C *pc; pb = dynamic_cast <B*>(pa); if (pb == NULL ){ cout <<"Downcasting failed: A* to B*" <<endl ; }else { cout <<"Downcasting successfully: A* to B*" <<endl ; pb -> func(); } pc = dynamic_cast <C*>(pa); if (pc == NULL ){ cout <<"Downcasting failed: A* to C*" <<endl ; }else { cout <<"Downcasting successfully: A* to C*" <<endl ; pc -> func(); }
输入输出
I/O类库中的常用流类:
类名
作用
在哪个头文件中声明
ios
抽象基类
iostream
istream ostream iostream
通用输入流和其他输入流的基类 通用输出流和其他输出流的基类 通用输入输出流和其他输入输出流的基类
iostream iostream iostream
ifstream ofstream fstream
输入文件流类 输出文件流类 输入输出文件流类
fstream fstream fstream
istrstream ostrstream strstream
输入字符串流类 输出字符串流类 输入输出字符串流类
strstream strstream strstream
文件中定义的4种流对象:
对象
含义
对应设备
对应的类
c语言中相应的标准文件
cin
标准输入流
键盘
istream_withassign
stdin
cout
标准输出流
屏幕
ostream_withassign
stdout
cerr
标准错误流
屏幕
ostream_withassign
stderr
clog
标准错误流
屏幕
ostream_withassign
stderr
log流对象也是标准错误流,它是console log的缩写。它的作用和cerr相同,都是在终端显示器上显示出错信息。区别:cerr是不经过缓冲区,直接向显示器上输出有关信息,而clog中的信息存放在缓冲区中,缓冲区满后或遇endl时向显示器输出。
输出格式控制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 cout <<"dec:" <<dec<<a<<endl ; cout <<"hex:" <<hex<<a<<endl ; cout <<"oct:" <<setbase(8 )<<a<<endl ; 运行结果如下: input a:34 ↙(输入a的值) dec:34 (十进制形式) hex:22 (十六进制形式) oct:42 (八进制形式) char *pt="China" ; cout <<setw(10 )<<pt<<endl ; cout <<setfill('*' )<<setw(10 )<<pt<<endl ; 运行结果如下: China (域宽为) *****China (域宽为,空白处以'*' 填充)
除了可以用控制符来控制输出格式外,还可以通过调用流对象cout中用于控制输出格式的成员函数来控制输出格式。用于控制输出格式的常用的成员函数。
流成员函数
与之作用相同的控制符
作用
precision(n)
setprecision(n)
设置实数的精度为n位
width(n)
setw(n)
设置字段宽度为n位
fill©
setfill©
设置填充宇符c
setf()
setiosflags()
设置输出格式状态,括号中应给出格式状态,内容与控制符setiosflags括号中的内容相同,如表13.5所示
unsetf()
resetioflags()
终止已设置的输出格式状态,在括号中应指定内容
流成员函数setf和控制符setiosflags括号中的参数表示格式状态,它是通过格式标志来指定的。格式标志在类ios中被定义为枚举值。因此在引用这些格式标志时要在前面加上类名ios和域运算符“::”。
格式标志
作用
ios::left
输出数据在本域宽范围内向左对齐
ios::right
输出数据在本域宽范围内向右对齐
ios::internal
数值的符号位在域宽内左对齐,数值右对齐,中间由填充字符填充
ios::dec
设置整数的基数为10
ios::oct
设置整数的基数为8
ios::hex
设置整数的基数为16
ios::showbase
强制输出整数的基数(八进制数以0打头,十六进制数以0x打头)
ios::showpoint
强制输出浮点数的小点和尾数0
ios::uppercase
在以科学记数法格式E和以十六进制输出字母时以大写表示
ios::showpos
对正数显示“+”号
ios::scientific
浮点数以科学记数法格式输出
ios::fixed
浮点数以定点格式(小数形式)输出
ios::unitbuf
每次输出之后刷新所有的流
ios::stdio
每次输出之后清除stdout, stderr
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 int main ( ) { int a=21 cout .setf(ios::showbase); cout <<"dec:" <<a<<endl ; cout .unsetf(ios::dec); cout .setf(ios::hex); cout <<"hex:" <<a<<endl ; cout .unsetf(ios::hex); cout .setf(ios::oct); cout <<"oct:" <<a<<endl ; cout .unseft(ios::oct); char *pt="China" ; cout .width(10 ); cout <<pt<<endl ; cout .width(10 ); cout .fill('*' ); cout <<pt<<endl ; double pi=22.0 /7.0 ; cout .setf(ios::scientific); cout <<"pi=" ; cout .width(14 ); cout <<pi<<endl ; cout .unsetf(ios::scientific); cout .setf(ios::fixed); cout .width(12 ); cout .setf(ios::showpos); cout .setf(ios::internal); cout .precision(6 ); cout <<pi<<endl ; return 0 ; }
成员函数put输出单个字符
put函数的参数可以是字符或字符的ASCII代码(也可以是一个整型表达式)。如
cout.put(65 + 32);
1 2 3 4 5 6 7 { char *a="BASIC" ; for (int i=4 ;i>=0 ;i--) cout .put(*(a+i)); cout .put('\n' ); return 0 ; }
除了使用cout.put函数输出一个字符外,还可以用putchar函数输出一个字符:
1 2 3 4 5 6 7 8 9 #include <iostream> //也可以用#include <stdio.h>,同时不要下一行 using namespace std ;int main ( ) { char *a="BASIC" ; for (int i=4 ;i>=0 ;i--) putchar (*(a+i)); putchar ('\n' ); }
get()函数读入一个字符
C语言中的getchar函数与流成员函数cin.get( )的功能相同,C++保留了C的这种用法,可以用getchar©从键盘读入一个字符赋给c。
1 2 3 4 5 6 7 8 int main ( ) { int c; cout <<"enter a sentence:" <<endl ; while ((c=cin .get())!=EOF) cout .put(c); return 0 ; }
*有一个参数的get函数 其调用形式为 cin.get(ch) 。其作用是从输入流中读取一个字符,赋给字符变量ch:
1 2 3 4 5 6 7 8 9 int main ( ) { char c; cout <<"enter a sentence:" <<endl ; while (cin .get(c)) {cout .put(c);} cout <<"end" <<endl ; return 0 ; }
有3个参数的get函数
其调用形式为
cin.get(字符数组, 字符个数n, 终止字符)
或
cin.get(字符指针, 字符个数n, 终止字符)
其作用是从输入流中读取n-1个字符,赋给指定的字符数组(或字符指针指向的数组),如果在读取n-1个字符之前遇到指定的终止字符,则提前结束读取。如果读取成功则函数返回true(真),如失败(遇文件结束符) 则函数返回false(假)。
1 2 3 4 5 6 7 8 int main ( ) { char ch[20 ]; cout <<"enter a sentence:" <<endl ; cin.get(ch,10,'\\n');//指定换行符为终止字符 cout <<ch<<endl ; return 0 ; }
getline()函数读入一行字符
getline函数的作用是从输入流中读取一行字符,其用法与带3个参数的get函数类似。即
cin.getline(字符数组(或字符指针), 字符个数n, 终止标志字符)
1 2 3 4 5 6 7 8 9 10 11 12 int main ( ) { char ch[20 ]; cout <<"enter a sentence:" <<endl ; cin >>ch; cout <<"The string read with cin is:" <<ch<<endl ; cin .getline(ch,20 ,'/' ); cout <<"The second part is:" <<ch<<endl ; cin .getline(ch,20 ); cout <<"The third part is:" <<ch<<endl ; return 0 ; }
其他istream类成员函数
eof 函数
eof是end of file的缩写,表示“文件结束”
1 2 3 4 5 6 7 8 int main ( ) { char c; while (!cin .eof( )) if ((c=cin .get( ))!=' ' ) cout .put(c); return 0 ; }
peek函数
peek是“观察”的意思,peek函数的作用是观测下一个字符。其调用形式为:
c=cin.peek( );
函数的返回值是指针指向的当前字符,但它只是观测,指针仍停留在当前位置,并不后移。
putback函数
其调用形式为
cin.putback(ch);
其作用是将前面用get或getline函数从输入流中读取的字符ch返回到输入流,插入到当前指针位置,以供后面读取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int main ( ) { char c[20 ]; int ch; cout <<"please enter a sentence:" <<endl ; cin .getline(c,15 ,'/' ); cout <<"The first part is:" <<c<<endl ; ch=cin .peek( ); cout <<"The next character(ASCII code) is:" <<ch<<endl ; cin .putback(c[0 ]); cin .getline(c,15 ,'/' ); cout <<"The second part is:" <<c<<endl ; return 0 ; }
ignore函数
其调用形式为
cin.ignore(n, 终止字符)
函数作用是跳过输入流中n个字符,或在遇到指定的终止字符时提前结束(此时跳过包括终止字符在内的若干字符)。如
ighore(5, ‘A’) //跳过输入流中个字符,遇’A’后就不再跳了
也可以不带参数或只带一个参数。如
ignore( ) // n默认值为,终止字符默认为EOF
相当于
ignore(1, EOF)
1 2 3 4 5 6 7 8 9 int main ( ) { char ch[20 ]; cin .get(ch,20 ,'/' ); cout <<"The first part is:" <<ch<<endl ; cin .get(ch,20 ,'/' ); cout <<"The second part is:" <<ch<<endl ; return 0 ; }
文件的打开与关闭
文件输入输出方式设置值:
方 式
作用
ios::in
以输入方式打开文件
ios::out
以输出方式打开文件(这是默认方式),如果已有此名字的文件,则将其原有内容全部清除
ios::app
以输出方式打开文件,写入的数据添加在文件末尾
ios::ate
打开一个已有的文件,文件指针指向文件末尾
ios: :trunc
打开一个文件,如果文件已存在,则删除其中全部数据,如文件不存在,则建立新文件。如已指定了 ios::out 方式,而未指定ios: :app,ios::ate,ios: :in,则同时默认此方式
ios:: binary
以二进制方式打开一个文件,如不指定此方式则默认为ASCII方式
ios::nocreate
打开一个已有的文件,如文件不存在,则打开失败。nocrcate的意思是不建立新文件
ios:: noreplace
如果文件不存在则建立新文件,如果文件已存在则操作失败,replace 的意思是不更新原有文件
ios::in l ios::out
以输入和输出方式打开文件,文件可读可写
ios:: out | ios::binary
以二进制方式打开一个输出文件
ios::in l ios::binar
以二进制方式打开一个输入文件
在对已打开的磁盘文件的读写操作完成后,应关闭该文件。关闭文件用成员函数close。
outfile.close( ); //将输出文件流所关联的磁盘文件关闭
关闭之后可以将文件流与其他磁盘文件建立关联,通过文件流对新的文件进行输入或输出。如
outfile.open(“f2.dat”,ios::app|ios::nocreate);
ASCII文件的读写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int main ( ) { int a[10 ]; ofstream outfile ("f1.dat" ,ios::out) ; if (!outfile) { cerr <<"open error!" <<endl ; exit (1 ); } cout <<"enter 10 integer numbers:" <<endl ; for (int i=0 ;i<10 ;i++) { cin >>a[i]; outfile<<a[i]<<" " ; } outfile.close(); return 0 ; }
参数 ios::out 可以省写。 如不写此项,则默认为ios::out。下面两种写法等价:
ofstream outfile(“f1.dat”, ios::out);
ofstream outfile(“f1.dat”);
系统函数exit用来结束程序运行。exit的参数为任意整数
在程序中用“cin>>”从键盘逐个读入10个整数,每读入一个就将该数向磁盘文件输出,输出的语句为:
outfile<<a[i]<<" ";
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 int main ( ) { int a[10 ],max,i,order; ifstream infile ("f1.dat" ,ios::in|ios::nocreate) ; if (!infile) { cerr <<"open error!" <<endl ; exit (1 ); } for (i=0 ;i<10 ;i++) { infile>>a[i]; cout <<a[i]<<" " ; } cout <<endl ; max=a[0 ]; order=0 ; for (i=1 ;i<10 ;i++) if (a[i]>max) { max=a[i]; order=i; } cout <<"max=" <<max<<endl <<"order=" <<order<<endl ; infile.close(); return 0 ; }
二进制文件的读写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int main( ) { student stud[3]={"Li",1001,18,'f',"Fun",1002,19,'m',"Wang",1004,17,'f'}; ofstream outfile("stud.dat",ios::binary); if(!outfile) { cerr<<"open error!"<<endl; abort( );//退出程序 } for(int i=0;i<3;i++) outfile.write((char*)&stud[i],sizeof(stud[i])); outfile.close( ); return 0; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int main( ) { student stud[3]; int i; ifstream infile("stud.dat",ios::binary); if(!infile) { cerr<<"open error!"<<endl; abort( ); } for(i=0;i<3;i++) infile.read((char*)&stud[i],sizeof(stud[i])); infile.close( ); for(i=0;i<3;i++) { cout<<"NO."<<i+1<<endl; cout<<"name:"<<stud[i].name<<endl; cout<<"num:"<<stud[i].num<<endl;; cout<<"age:"<<stud[i].age<<endl; cout<<"sex:"<<stud[i].sex<<endl<<endl; } return 0; }
文件流与文件指针有关的成员函数:
成员函数
作 用
gcount()
返回最后一次输入所读入的字节数
tellg()
返回输入文件指针的当前位置
seekg(文件中的位置)
将输入文件中指针移到指定的位置
seekg(位移量, 参照位置)
以参照位置为基础移动若干字节
tellp()
返回输出文件指针当前的位置
seekp(文件中的位置)
将输出文件中指针移到指定的位置
seekp(位移量, 参照位置)
以参照位置为基础移动若干字节
字符串流的读写
建立输入输出字符串流对象:
strstream类提供的构造函数的原型为:
strstream::strstream(char *buffer,int n,int mode);
可以用以下语句建立输入输出字符串流对象:
strstream strio(ch3,sizeof(ch3),ios::in|ios::out);
作用是建立输入输出字符串流对象,以字符数组ch3为输入输出对象,流缓冲区大小与数组ch3相同。
1 2 3 4 5 6 7 8 9 10 int main ( ) { student stud[3 ]={1001 ,"Li" ,78 ,1002 ,"Wang" ,89.5 ,1004 ,"Fun" ,90 }; char c[50 ]; ostrstream strout (c,30 ) ; for (int i=0 ;i<3 ;i++) strout<<stud[i].num<<stud[i].name<<stud[i].score; strout<<ends; cout <<"array c:" <<c<<endl ; }